
#include <regex>
#include <BDate>
#include <BTime>
#include <BFolder>
#include "IGlobal.h"
#include "member_BFolder.h"
#include "member_BEntry.h"

using namespace BWE;

#define member (*(member_BFolder *)_ptr)
#define member_allocate() _ptr = new member_BFolder(this)
#define member_release() delete (member_BFolder *)_ptr

#ifdef linux

BString BFolder::AbsolutePath(const BString &path)
{
	char abpath[80];
	realpath(path.cstr(), abpath);
	return abpath;
}
BString BFolder::RelativePath(const BString &path)
{
	char buffer[256] = {0};
	getcwd(buffer, 256);
	if (path.beginWith(buffer))
	{
		BString output(path);
		output.remove(buffer);
		output.removeHead('/');
		return output;
	}
	return path;
}
bool BFolder::Create(const BString &path, Attrib attrib)
{
	mode_t mode = 0x0755;
	int ret = mkdir(path.cstr(), mode);
	return ret == 0;
}
bool BFolder::Remove(const BString &path)
{
	int ret = rmdir(path.cstr());
	return ret == 0;
}
bool BFolder::Rename(const BString &path, const BString &newName)
{
	if (path.contain('/'))
	{
		BString newFileName = path.path();
		newFileName << '/' << newName;
		int ret = ::rename(path.cstr(), newFileName.cstr());
		return ret == 0;
	}
	int ret = ::rename(path.cstr(), newName.cstr());
	return ret == 0;
}
bool BFolder::Exist(const BString &path)
{
	Attrib attrib = BEntry::Check(path);
	return attrib & Attrib_Folder;
}
bool BFolder::Move(const BString &fileName, const BString &path)
{
	BString newFileName = path;
	newFileName << '/' << fileName.name();
	int ret = ::rename(fileName.cstr(), newFileName.cstr());
	return ret == 0;
}

bool BFolder::open()
{
	if (member.dir)
		return true;

	member.entries.clear();
	member.dir = opendir(member.path.cstr());
	if (!member.dir)
		return false;

	struct tm dtime;
	struct stat st;
	while (dirent *de = readdir(member.dir))
	{
		BString name = de->d_name;
		if (name == '.' || name == "..")
			continue;
		BEntry *entry = new BEntry();
		entry->setName(name);
		entry_member(entry)->path = member.path;
		entry_member(entry)->path << '/' << name;
		const BString &filePath = entry_member(entry)->path;
		if (::stat(entry_member(entry)->path.cstr(), &st) == 0)
		{
			entry_member(entry)->size = st.st_size;
			localtime_r(&st.st_mtim.tv_sec, &dtime);
			entry_member(entry)->date.set(dtime.tm_year + 1900, dtime.tm_mon + 1, dtime.tm_mday);
			entry_member(entry)->time.set(dtime.tm_hour, dtime.tm_min, dtime.tm_sec);
		}
		Attrib attribs = Attrib_None;
		switch (de->d_type)
		{
		case DT_REG:
			attribs = Attrib_File;
			break;
		case DT_DIR:
			attribs = Attrib_Folder;
			break;
		case DT_BLK:
			attribs = Attrib_File;
			break;
		case DT_FIFO:
			attribs = Attrib_Pipe;
			break;
		case DT_LNK:
			attribs = Attrib_Link;
			break;
		case DT_SOCK:
			attribs = Attrib_Socket;
			break;
		default:
			attribs = Attrib_None;
			break;
		}
		entry_member(entry)->attrib = attribs;
		member.entries.append(entry);
	}
	return member.dir;
}

bool BFolder::close()
{
	if(member.dir)
	{
		closedir(member.dir);
		member.dir = 0;
		member.entries.clear();
		return true;
	}
	return false;
}

bool BFolder::isOpened() const
{
	return member.dir;
}

#endif

#ifdef _WIN32

BString BFolder::AbsolutePath(const BString &path)
{
	WCHAR buffer[256] = {0};
	DWORD len = 0;
	if (path.endWith(':'))
	{
		BString temp = path;
		temp += '\\';
		WCHAR wstr[256];
		bMbsToWcs(temp.cstr(), wstr);
		len = ::GetFullPathName(wstr, 256, buffer, 0);
	}
	else
	{
		WCHAR wstr[256];
		bMbsToWcs(path.cstr(), wstr);
		len = ::GetFullPathName(wstr, 256, buffer, 0);
	}
	char cstr[256];
	len = bWcsToMbs(buffer, cstr);
	for (DWORD i = 0; i < len; i++)
	{
		if (cstr[i] == '\\')
			cstr[i] = '/';
	}
	return cstr;
}
BString BFolder::RelativePath(const BString &path)
{
	wchar_t buffer[256] = {0};
	DWORD len = GetCurrentDirectory(256, buffer);
	for (DWORD i = 0; i < len; i++)
	{
		if (buffer[i] == '\\')
			buffer[i] = '/';
	}
	char cstr[256];
	bWcsToMbs(buffer, cstr);
	if (path.beginWith(cstr))
	{
		BString result(path);
		result.remove(cstr);
		result.removeHead('/');
		return result;
	}
	return path;
}
bool BFolder::Create(const BString &path, Attrib attrib)
{
	wchar_t wstr[256];
	if (path.contain('/'))
	{
		BString temp;
		int end = path.find('/');
		while (end < path.size())
		{
			path.substr(temp, 0, end);
			if (!BFolder::Exist(temp))
			{
				if (path.hasUtf(8))
				{
					bMbsToWcs(temp.cstr(), wstr);
					if (FALSE == CreateDirectoryW(wstr, 0))
						return false;
				}
				else
				{
					if (FALSE == CreateDirectoryA(temp.cstr(), 0))
						return false;
				}
			}
			end = path.find('/', end + 1);
		}
	}
	if (path.hasUtf(8))
	{
		bMbsToWcs(path.cstr(), wstr);
		if (FALSE == CreateDirectoryW(wstr, 0))
			return false;
		SetFileAttributesW(wstr, ConvertFromAttrib(attrib));
	}
	else
	{
		if (FALSE == CreateDirectoryA(path.cstr(), 0))
			return false;
		SetFileAttributesA(path.cstr(), ConvertFromAttrib(attrib));
	}
	return true;
}
bool BFolder::Remove(const BString &path)
{
	if (RemoveDirectoryA(path.cstr()) == TRUE)
		return true;
	wchar_t from[256];
	bMbsToWcs(path.cstr(), from);
	SHFILEOPSTRUCT fop;
	fop.pFrom = from;
	fop.fFlags = FOF_NOCONFIRMATION;
	fop.hNameMappings = NULL;
	fop.hwnd = NULL;
	fop.lpszProgressTitle = NULL;
	fop.pTo = NULL;
	fop.wFunc = FO_DELETE;
	int iret = SHFileOperation(&fop);
	return iret == 0;
}
bool BFolder::Rename(const BString &path, const BString &newPath)
{
	if (path.contain('/'))
	{
		BString temp = path.path();
		temp << '/' << newPath;
		BOOL ret = MoveFileA(path.cstr(), temp.cstr());
		return ret == TRUE;
	}
	BOOL ret = MoveFileA(path.cstr(), newPath.cstr());
	return ret == TRUE;
}
bool BFolder::Move(const BString &path, const BString &name)
{
	BString filePath = name;
	if (name.endWith('/'))
		filePath << '/';
	filePath << path.name();
	BOOL ret = MoveFileA(path.cstr(), filePath.cstr());
	return ret == TRUE;
}
bool BFolder::Exist(const BString &path)
{
	Attrib attrib = BEntry::Check(path);
	return attrib & Attrib_Folder;
}

bool BFolder::open()
{
	if (member.isOpened)
		return true;
	if (member.path.empty())
		return false;
	member.entries.clear();

	BString pathStr = member.path;
	if (member.path.last() != '/')
		pathStr << '/';
	pathStr << '*';

	WCHAR wpath[256];
	bMbsToWcs(pathStr.cstr(), wpath);
	WIN32_FIND_DATA EntryData;
	HANDLE hSearch = FindFirstFile(wpath, &EntryData);
	if (hSearch == INVALID_HANDLE_VALUE)
		return false;

	BString fileName;
	char cstr[256];
	while (FindNextFile(hSearch, &EntryData))
	{
		bWcsToMbs(EntryData.cFileName, cstr);
		fileName = cstr;
		if (fileName == '.' || fileName == "..")
			continue;
		Attrib attrib = ConvertToAttrib(EntryData.dwFileAttributes);
		if (member.attribFilter & attrib)
		{
			BEntry *entry = new BEntry();
			entry->setName(fileName);
			entry_member(entry)->attrib = attrib;
			entry_member(entry)->path = member.path;
			entry_member(entry)->path << '/' << fileName;
			SYSTEMTIME systime;
			FileTimeToSystemTime(&EntryData.ftLastWriteTime, &systime);
			systime.wHour = (systime.wHour + 8) % 24;
			BDate date(systime.wYear, systime.wMonth, systime.wDay);
			BTime time(systime.wHour, systime.wMinute, systime.wSecond);
			entry->setDate(date);
			entry->setTime(time);
			entry->setSize(EntryData.nFileSizeLow);
			member.entries.append(entry);
		}
	}
	FindClose(hSearch);
	member.isOpened = true;
	return true;
}
bool BFolder::close()
{
	if (member.isOpened)
	{
		member.isOpened = false;
		member.entries.clear();
		return true;
	}
	return false;
}

bool BFolder::isOpened() const
{
	return member.isOpened;
}

#endif

bool Search(const BString& path, const BString& name, BString& result)
{
	BFolder folder(path);
	if (!folder.open())
		return false;
	for (int i = 0; i < folder.entryCount(); i++)
	{
		BEntry* entry = folder.entry(i);
		if (entry->name() == name)
		{
			result = entry->path();
			return true;
		}
	}
	for (int i = 0; i < folder.entryCount(); i++)
	{
		BEntry* entry = folder.entry(i);
		if (entry->attrib() & Attrib_Folder)
		{
			if (::Search(entry->path(), name, result))
				return true;
		}
	}
	return false;
}
bool Search(const BString& path, const std::regex& regex, BString& result)
{
	BFolder folder(path);
	if (!folder.open())
		return false;
	for (int i = 0; i < folder.entryCount(); i++)
	{
		BEntry* entry = folder.entry(i);
		std::string text = entry->name().cstr();
		std::smatch matchResults;
		if (std::regex_search(text, matchResults, regex))
		{
			result = entry->path();
			return true;
		}
	}
	for (int i = 0; i < folder.entryCount(); i++)
	{
		BEntry* entry = folder.entry(i);
		if (entry->attrib() & Attrib_Folder)
		{
			if (::Search(entry->path(), regex, result))
				return true;
		}
	}
	return false;
}
bool BFolder::Search(const BString& path, const BString& name, BString& result)
{
	result.reset();
	try
	{
		std::regex regex(name.cstr());
		return ::Search(path, regex, result);
	}
	catch (const std::regex_error&)
	{
		return ::Search(path, name, result);
	}
	return false;
}

BFolder::BFolder()
{
	member_allocate();
}
BFolder::BFolder(const BString &folderPath, Attrib attribFilter)
{
	member_allocate();
	setPath(folderPath);
	setAttribFilter(attribFilter);
}
BFolder::~BFolder()
{
	close();
	if (member.notifying)
	{
		if (App_LockGlobalMutex())
		{
			app_foldersNotifying.remove(this);
			App_UnlockGlobalMutex();
		}
	}
	member_release();
}

void BFolder::setNotifying(bool notifying)
{
	if (member.notifying != notifying)
	{
		if (App_LockGlobalMutex())
		{
			member.notifying = notifying;
			if (notifying)
			{
				app_foldersNotifying.insert(this);
			}
			App_UnlockGlobalMutex();
		}
	}
}
bool BFolder::notifying() const
{
	return member.notifying;
}

void BFolder::setPath(const BString &path)
{
	if (member.path != path)
	{
		close();
		member.path = path;
		this->emit(Signal_PathChanged, path);
	}
}
const BString &BFolder::path() const
{
	return member.path;
}

void BFolder::setAttribFilter(Attrib attribFilter)
{
	if (member.attribFilter != attribFilter)
	{
		member.attribFilter = attribFilter;
	}
}
Attrib BFolder::attribFilter() const
{
	return member.attribFilter;
}

void BFolder::setNameFilter(const BString &nameFilter)
{
	if (member.nameFilter != nameFilter)
	{
		member.nameFilter = nameFilter;
	}
}
const BString &BFolder::nameFilter() const
{
	return member.nameFilter;
}

BEntry *BFolder::addEntry(const BString &name)
{
	BEntry *entry = new BEntry(name);
	member.entries.append(entry);
	return entry;
}
void BFolder::addEntry(BEntry *entry)
{
	if (member.entries.append(entry))
	{
	}
}

BEntry *BFolder::insertEntry(int index, const BString &name)
{
	BEntry *entry = new BEntry(name);
	member.entries.insert(index, entry);
	return entry;
}
void BFolder::insertEntry(int index, BEntry *entry)
{
	member.entries.insert(index, entry);
}

bool BFolder::removeEntry(const BString &name)
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		if (member.entries[i]->name() == name)
			return member.entries.remove(i);
	}
	return false;
}
bool BFolder::removeEntry(const BEntry *entry)
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		if (member.entries[i] == entry)
			return member.entries.remove(i);
	}
	return false;
}
bool BFolder::removeEntry(int index)
{
	return member.entries.remove(index);
}

bool BFolder::contain(const BString &name) const
{
	for (int i = 0; i < member.entries.size(); i++)
		if (member.entries[i]->name() == name)
			return true;
	return false;
}
bool BFolder::contain(const BEntry *entry) const
{
	for (int i = 0; i < member.entries.size(); i++)
		if (member.entries[i] == entry)
			return true;
	return false;
}

int BFolder::entryCount() const
{
	return member.entries.size();
}

BEntry *BFolder::entry(int index)
{
	return member.entries(index);
}
const BEntry *BFolder::entry(int index) const
{
	return member.entries(index);
}

BEntry *BFolder::entry(const BString &name)
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		BEntry *entry = member.entries[i];
		if (entry->name() == name)
			return entry;
	}
	return 0;
}
const BEntry *BFolder::entry(const BString &name) const
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		const BEntry *entry = member.entries[i];
		if (entry->name() == name)
			return entry;
	}
	return 0;
}

BEntry *BFolder::find(const BString &desc)
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		BEntry *entry = member.entries[i];
		if (entry->name().contain(desc))
			return entry;
	}
	return 0;
}
const BEntry *BFolder::find(const BString &desc) const
{
	for (int i = 0; i < member.entries.size(); i++)
	{
		const BEntry *entry = member.entries[i];
		if (entry->name().contain(desc))
			return entry;
	}
	return 0;
}

BString BFolder::absolutePath(const BString &path)
{
	return AbsolutePath(member.path + path);
}
BString BFolder::relativePath(const BString &path)
{
	if (path.beginWith(member.path))
	{
		BString result(path);
		result.remove(member.path);
		result.removeHead('/');
		return result;
	}
	return path;
}
